#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define mes_len 256 //固定长度
int sockfd = 0;

int str_ex(char in_str[])
{
    char ex_str[mes_len] = "";//创建临时字符数组
    strcpy(ex_str,in_str);//cp传入的字符数组
    char *str1 = strtok(ex_str,".");//通过strtok获取每个部分的内容
    char *str2 = strtok(NULL,".");
    char *str3 = strtok(NULL,".");
    char *str4 = strtok(NULL,",");
    char *str5 = strtok(NULL,",");

    char ex_num[mes_len] = "";//创建一个字符数组,用来存储前面被分格出来的全部字符
    //因为strtok分格字符串时,如果全部的内容已经被前面的strtok分格完,那么当前的char*变量就等同于未初始化的指针变量,地址为NULL,指向的内容自然也为NULL
    if (str1!=NULL && str2!=NULL && str3!=NULL && str4!=NULL && str5!=NULL)//这里判断strtok分格出来的的字符串是否为NULL
    {
        sprintf(ex_num,"%s%s%s%s%s",str1,str2,str3,str4,str5);//拼接全部分格出来的字符串,以供后面检查
    }
    else//如果有字符串为NULL,那么打印提示,并直接返回
    {
        printf("请按正确格式输入内容!格式:IP,端口!\n");
        return -1;
    }
    for (int i = 0; i < strlen(ex_num); i++)
    {   //通过循环和下标,对前面拼接的字符串进行逐个检查,如果不为数字,就打印提示并返回
        if (ex_num[i]<'0'||ex_num[i]>'9')
        {
            printf("请按正确格式输入内容!格式:IP,端口!\n");
            return -1;
        }
    }
     //判断ip分格出来的字符串是否超过255,因为ip的每个部分都是一个字节,最高只能到255
    if (atoi(str1)>255||atoi(str2)>255||atoi(str3)>255||atoi(str4)>255)
    {
        printf("请按正确格式输入内容!格式:IP,端口!\n");
        return -1;
    }
    //判断端口号的范围,不在规定范围内就打印提示并返回
    if (atoi(str5)>65535||atoi(str5)<=0)
    {   
        printf("端口号范围在1~65535!请重新输入!\n");
        return -1;
    }

    return 0;
}


void *recv_mes(void* agv)//接受消息函数
{
    while(1)
    {
        char buf[mes_len] = "";//存储接受到的消息
        struct sockaddr addr_recv;//创建存储接受到的ip和端口号的结构体
        int addr_recv_len = sizeof(addr_recv);//对结构体清零
        int len = recv(sockfd,buf,mes_len,0);

        printf("%s(%d)\n",buf,len);//打印接受到的消息
    }
    return 0;
}


void *send_mes(void* agv)//发送消息函数
{
   

    while (1)//循环保证线程一直处于运行状态
    {
        char scanf_mes[mes_len] = "";//记录输入的内容
        scanf("%s",scanf_mes);//输入内容
        if (strcmp(scanf_mes,"close")==0)return 0;//判断输入的是否是关闭指令
        unsigned long send_mess_len = strlen(scanf_mes);//后面sendto发送的长度
        send(sockfd,scanf_mes,send_mess_len,0);//将需要发送的内容通过sockfd发到in_addr指定的ip
    }
    return 0;
}

int argv_ex(int argc_in,char const *argv_in)
{
    //此函数用来判断输入参数的个数及类型,所以需要一个参数,如果缺少参数或者多输入参数,则会打印提示并退出
    if ((argc_in != 2))
    {
        printf("输入格式不对!格式: a.out 端口\n");
        return -1;
    }
    for (int i = 0; i < strlen(argv_in); i++)
    {   //通过循环和下标,对输入的参数进行逐个检查,如果不为数字,就打印提示并返回
        if (argv_in[1]<'0'||argv_in[1]>'9')
        {
            printf("参数必须是数值!\n");
            return -1;
        }
    }
    //判断输入的参数是否在范围内,否则打印提示并退出
    if ((atoi(argv_in)>65535) || (atoi(argv_in)<1))
    {
        printf("输入参数不对!参数为端口,数值范围为1-65535!\n");
        return -1;
    }
    return 0;
}

int main(int argc, char const *argv[])
{
    int ex_argv_num = argv_ex(argc,argv[1]);//检查输入参数
    if (ex_argv_num < 0)return 0;
    if ((sockfd=socket(AF_INET,SOCK_STREAM,0)) < 0)//创建套接字描述符,并判断是否创建成功,如果未创建成功就直接结束并打印错误信息
    {
        perror("sockfd");
        return 0;
    }
    struct sockaddr_in bind_addr;//创建需要绑定的本地ip结构体
    bzero(&bind_addr,sizeof(bind_addr));//初始化本地ip结构体
    bind_addr.sin_family = AF_INET;//协议
    bind_addr.sin_port = htons(atoi(argv[1]));//通过输入的参数绑定本地端口
    bind_addr.sin_addr.s_addr = INADDR_ANY;//INADDR_ANY 绑定本地机的所有IP地址 
    // 为套接字描述符绑定本地ip和端口并通过bind的返回值判断是否绑定成功,未成功就关闭描述符并且打印错误信息以及直接结束
    if (bind(sockfd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0)
    {
        perror("bind");
        close(sockfd);
        return 0;
    }

    char scanf_addr[mes_len] = "";//记录目标ip和接口
    while (1)
    {
        scanf("%s",scanf_addr);//输入目标ip和接口
        if (strcmp(scanf_addr,"close")==0)return 0;//判断输入的是否是关闭指令
        int ex = str_ex(scanf_addr);//判断输入的内容是否符合标准
        if (ex != -1)break;
        else memset(scanf_addr,0,mes_len);//如果不符合标准就重置缓冲区继续重新输入
    }
    char* ip_str = strtok(scanf_addr,",");//对输入的内容进行解析,此处为ip
    char* port_str = strtok(NULL,",");//端口号
    struct sockaddr_in dst_addr;//需要绑定的结构体
    dst_addr.sin_family = AF_INET;//协议
    dst_addr.sin_addr.s_addr = inet_addr(ip_str);//ip
    dst_addr.sin_port = htons(atoi(port_str));//端口
    connect(sockfd,(struct sockaddr*)&dst_addr,sizeof(dst_addr));//绑定套接字描述符和结构体
   
    pthread_t thread1;//创建第一个线程号
    pthread_t thread2;//创建第二个线程号
    pthread_create(&thread2,NULL,recv_mes,NULL);
    pthread_create(&thread1,NULL,send_mes,NULL);//绑定线程号和函数,并且调用线程,传入参数描述符
    pthread_join(thread1,NULL);//主进程等待发送线程的结束
    pthread_cancel(thread2);
    pthread_join(thread2,NULL);//主进程等待发送线程的结束
    close(sockfd);//最后关闭描述符
    return 0;
}

